#include <sys/lpc.h>
#include <os/debug.h>
#include <os/pipe.h>
#include <os/portcom.h>
#include <os/memcache.h>
#include <lib/unistd.h>
#include <lib/errno.h>
#include <lib/type.h>
#include <lib/string.h>
#include <lib/bitop.h>

lpc_parcel_t LpcParcelGet()
{
    lpc_parcel_t parcel = KMemAlloc(PORT_MSG_SIZE);
    if (parcel)
    {
        memset(parcel, 0, PORT_MSG_SIZE);
        return parcel;
    }
    return NULL;
}

int LpcParcelFree(lpc_parcel_t parcel)
{
    if (parcel)
    {
        KMemFree(parcel);
        return 0;
    }
    return -1;
}

static uint16_t LpcParcelGetArgType(lpc_parcel_t parcel, uint16_t index)
{
    return parcel->header.argtype[index];
}

static int LpcParcelAllocArgSolt(lpc_parcel_t parcel)
{
    int i;

    if (!parcel)
        return -1;

    for (i = 0; i < LPC_PARCEL_ARG_NUM; i++)
    {
        if (!test_bit(i, (uint64_t*)&parcel->header.argused))
        {
            break;
        }
    }
    if (i >= LPC_PARCEL_ARG_NUM)
        return -1;
    return i;
}

static int LpcParcelFindArgSolt(lpc_parcel_t parcel, uint16_t type)
{
    int i;

    if (!parcel)
        return -1;
    for (i = 0; i < LPC_PARCEL_ARG_NUM; i++)
    {
        if (test_bit(i, (uint64_t*)&parcel->header.argused))
        {
            if (LpcParcelGetArgType(parcel, i) == type && parcel->header.arglen[i] >= 0)
            {
                break;
            }
        }
    }
    if (i >= LPC_PARCEL_ARG_NUM)
        return -1;
    return i;
}

static void LpcParcelSetArg(lpc_parcel_t parcel, uint16_t index, uint32_t data, uint16_t len, uint16_t type)
{
    parcel->header.arglen[index] = len;
    parcel->header.args[index] = data;
    parcel->header.argtype[index] = type;
    parcel->header.argused |= (1 << index);
}

static void LpcParcelClearArg(lpc_parcel_t parcel, uint16_t index)
{
    parcel->header.arglen[index] = 0;
    parcel->header.args[index] = 0;
    parcel->header.argtype[index] = LPC_PARCEL_ARG_UNKNOW;
    parcel->header.argused &= ~(1 << index);
}

int LpcParcelWriteString(lpc_parcel_t parcel, char *str)
{
    int i = LpcParcelAllocArgSolt(parcel);
    int len = strlen(str);
    int size = parcel->header.size + len + 1;
    char *buff;
    if (i < 0)
        return -1;
    if (size > LPC_PARCEL_BUFF_SIZE)
        return -1;
    LpcParcelSetArg(parcel, i, parcel->header.size, len + 1, LPC_PARCEL_ARG_STRING);
    buff = &parcel->data[parcel->header.size]; //write data
    memcpy(buff, str, len);
    buff[len] = '\0';
    parcel->header.size = size; //update data area size
    return 0;
}

int LpcParcelWriteSequence(lpc_parcel_t parcel, void *buff, size_t len)
{
    int i = LpcParcelAllocArgSolt(parcel);
    int size = parcel->header.size + len + 1;
    char *p;
    if (i < 0)
        return -1;
    if (size > LPC_PARCEL_BUFF_SIZE)
        return -1;
    LpcParcelSetArg(parcel, i, parcel->header.size, len , LPC_PARCEL_ARG_SEQUEUECE);
    p = &parcel->data[parcel->header.size];
    if (p)
        memcpy(p, buff, len);
    p[len] = '\0';
    parcel->header.size = size;
}

int LpcParcelWriteInt(lpc_parcel_t parcel, uint32_t value)
{
    int i = LpcParcelAllocArgSolt(parcel);
    if (i < 0)
        return -1;
    LpcParcelSetArg(parcel, i, value, sizeof(value), LPC_PARCEL_ARG_INT);
    return 0;
}

int LpcParcelWriteLong(lpc_parcel_t parcel, uint64_t value)
{
    int i = LpcParcelAllocArgSolt(parcel);
    if (i < 0)
        return -1;
    LpcParcelSetArg(parcel, i, value, sizeof(value), LPC_PARCEL_ARG_LONG);
    return 0;
}

int LpcParcelWriteShort(lpc_parcel_t parcel, uint16_t value)
{
    int i = LpcParcelAllocArgSolt(parcel);
    if (i < 0)
        return -1;
    LpcParcelSetArg(parcel, i, value, sizeof(value), LPC_PARCEL_ARG_SHORT);
    return 0;
}

int LpcParcelWriteChar(lpc_parcel_t parcel, uint8_t value)
{
    int i = LpcParcelAllocArgSolt(parcel);
    if (i < 0)
        return -1;
    LpcParcelSetArg(parcel, i, value, sizeof(value), LPC_PARCEL_ARG_CHAR);
    return 0;
}

int LpcParcelReadInt(lpc_parcel_t parcel, uint32_t *value)
{
    int i = LpcParcelFindArgSolt(parcel, LPC_PARCEL_ARG_INT);
    if (i < 0)
        return -1;
    if (value)
        *value = parcel->header.args[i];
    LpcParcelClearArg(parcel, i);
    return 0;
}

int LpcParcelReadLong(lpc_parcel_t parcel, uint64_t *value)
{
    int i = LpcParcelFindArgSolt(parcel, LPC_PARCEL_ARG_LONG);
    if (i < 0)
        return -1;
    if (value)
        *value = parcel->header.args[i];
    LpcParcelClearArg(parcel, i);
    return 0;
}

int LpcParcelReadShort(lpc_parcel_t parcel, uint16_t *value)
{
    int i = LpcParcelFindArgSolt(parcel, LPC_PARCEL_ARG_SHORT);
    if (i < 0)
        return -1;
    if (value)
        *value = parcel->header.args[i];
    LpcParcelClearArg(parcel, i);
    return 0;
}

int LpcParcelReadChar(lpc_parcel_t parcel, uint8_t *value)
{
    int i = LpcParcelFindArgSolt(parcel, LPC_PARCEL_ARG_CHAR);
    if (i < 0)
        return -1;
    if (value)
        *value = parcel->header.args[i];
    LpcParcelClearArg(parcel, i);
    return 0;
}

int LpcParcelReadString(lpc_parcel_t parcel, char **str)
{
    int i = LpcParcelFindArgSolt(parcel, LPC_PARCEL_ARG_STRING);
    if (i < 0)
        return -1;
    if (str)
        *str = &parcel->data[parcel->header.size];
    LpcParcelClearArg(parcel, i);
    return 0;
}

int LpcParcelReadSequence(lpc_parcel_t parcel, void **buff, uint32_t *len)
{
    int i = LpcParcelFindArgSolt(parcel, LPC_PARCEL_ARG_SEQUEUECE);
    if (i < 0)
        return -1;
    if (buff)
        *buff = (void *)&parcel->data[parcel->header.size];
    if (len)
        *len = parcel->header.arglen[i];
    LpcParcelClearArg(parcel, i);
    return 0;
}

int LpcParcelReadSequenceBuff(lpc_parcel_t parcel, void *buff, uint32_t *len)
{
    int i = LpcParcelFindArgSolt(parcel, LPC_PARCEL_ARG_SEQUEUECE);
    if (i < 0)
        return -1;
    if (len)
        *len = (uint32_t)parcel->header.arglen[i];
    if (buff)
        memcpy(buff, &parcel->data[parcel->header.size], parcel->header.arglen[i]);

    LpcParcelClearArg(parcel, i);
    return 0;
}

//bind port and loop receive msg then call func to deal and reply
int LpcDoEcho(uint32_t port, lpc_handler_t func)
{
    port_msg_t *msg_recv = NULL;
    port_msg_t *msg_reply = NULL;
    lpc_parcel_t recv_parcel;
    lpc_parcel_t reply_parcel;
    int res;

    msg_recv = (port_msg_t*)KMemAlloc(sizeof(port_msg_t));
    if (!msg_recv)
        return -1;
    msg_reply = (port_msg_t*)KMemAlloc(sizeof(port_msg_t));
    if (!msg_reply)
        return -1;
    while (1)
    {
        PortMsgReset(msg_recv);
        PortMsgReset(msg_reply);
        if (SysPortComRequest(port, msg_recv) < 0)
        {
            continue;
        }
        //call func
        recv_parcel = (lpc_parcel_t)msg_recv->data;
        reply_parcel =(lpc_parcel_t) msg_reply->data;
        if (func)
        {
            res = func(recv_parcel->code, recv_parcel, reply_parcel);
            if (!res)
            {
                KPrint("port %d do service func failed!\n", port);
            }
        }
        //create reply header
        PortMsgCopyHeader(msg_recv, msg_reply);
        msg_reply->header.size = sizeof(port_msg_header_t);
        msg_reply->header.size += sizeof(_lpc_parcel_t) + reply_parcel->header.size;
        if (SysPortComReply(port, msg_reply))
            KPrint(PRINT_ERR "reply port %d failed!\n", port);
    }
}

int LpcEcho(uint32_t port, lpc_handler_t func)
{
    if (SysPortComBind(port, 0) < 0)
        return -1;
    return LpcDoEcho(port, func);
}

int LpcEchoGroup(uint32_t port, lpc_handler_t func)
{
    if (SysPortComBind(port, PORT_BIND_GROUP) < 0)
        return -1;
    return LpcDoEcho(port, func);
}

int LpcCall(uint32_t port, uint32_t code, lpc_parcel_t data, lpc_parcel_t reply)
{
    port_msg_t *msg = KMemAlloc(sizeof(port_msg_t));
    lpc_parcel_t reply_msg;
    int msglen;

    if (!msg)
        return -1;
    //bind rand port
    if (SysPortComBind(-1, PORT_BIND_ONCE) < 0)
        return -1;
    PortMsgReset(msg);
    data->code = code;
    msglen = sizeof(_lpc_parcel_t) + data->header.size;
    memcpy(msg->data, data, msglen);
    //set msg header size
    msg->header.size = sizeof(port_msg_header_t);
    msg->header.size += msglen;
    //send port request 
    if (SysPortComRequest(port, msg) < 0)
    {
        KMemFree(msg);
        return -1;
    }
    if (reply) //make reply msg
    {
        reply_msg = (lpc_parcel_t)msg->data;
        memcpy(reply, reply_msg, sizeof(_lpc_parcel_t) + reply_msg->header.size);
    }
    KMemFree(msg);
    return 0;
}